﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Collections.Generic;
using Liquifier;

namespace LiquifierTest
{
    public partial class LiquiferAttributesTest
    {
        #region Helpers

        public T DoLiquifify<T>(T obj)
        {
            MemoryTributary s = new MemoryTributary();
            Liquifer.Liquify(obj, s);
            s.Position = 0;
            T cr = (T)Liquifer.Solidify(s);
            return cr;
        }

        static Random r = new Random();

        public static string RandomString()
        {
            return r.Next().ToString();
        }

        #endregion

        #region Basic Test

        [Serializable]
        public struct BasicTestStruct
        {
            float x;
            float y;
            float z;
            float w;

            public BasicTestStruct(float x, float y, float z, float w)
            {
                this.x = x;
                this.y = y;
                this.z = z;
                this.w = w;
            }
        }

        [Liquifiable]
        public class BasicTestClass
        {
            public int valueOne = 10;
            public int valueTwo = 20;
            public string stringOne = "defaultstring";
            public byte[] arrayOne;
            public BasicTestStruct valueThree;
            public BasicTestStruct[] arrayTwo;
            public BasicTestStruct[] emptyArray;
        }

        /// <summary>
        /// A test to ensure that Liquifier can serialise and deserialise basic top level values
        /// </summary>
        [TestMethod()]
        public void Does_Serialise_Basic_Types_Such_As_Integers_Strings_Structs_And_Arrays()
        {
            BasicTestClass before = new BasicTestClass();
            
            //Ensure we can restore basic values (int and string)
            before.valueOne = 60;
            before.stringOne = "ssv";
            
            //Ensure we can restore arrays of basic types (byte)
            before.arrayOne =  new byte[] { 0, 1, 2, 4 };
            
            //Ensure we can restore custom value types (they must be serializeable but not necessarily liquifiable)
            before.valueThree = new BasicTestStruct(0, 1, 2, 4);

            //Ensure we can restore arrays of custom value types
            before.arrayTwo = new BasicTestStruct[] { new BasicTestStruct(0, 2, 3, 4), new BasicTestStruct(5, 6, 7, 8), new BasicTestStruct(9, 10, 11, 12) };

            before.emptyArray = new BasicTestStruct[] { };

            BasicTestClass after = DoLiquifify<BasicTestClass>(before);

            Assert.AreEqual(before.valueOne, after.valueOne);
            Assert.AreEqual(before.valueTwo, after.valueTwo);
            Assert.AreEqual(before.stringOne, after.stringOne);
            CollectionAssert.AreEqual(before.arrayOne, after.arrayOne);
            Assert.AreEqual(before.valueThree, after.valueThree);
            CollectionAssert.AreEqual(before.arrayTwo, after.arrayTwo);

            Assert.AreEqual(after.emptyArray.Length, 0);
        }

        #endregion

        #region Liquify Superclass Members

        public class SuperSuperClassA
        {
            public string superSuperClassMemberOne;
            private string superSuperClassMemberTwo;
            internal string superSuperClassMemberThree;
            protected string superSuperClassMemberFour;

            public virtual void InitialiseOne()
            {
                superSuperClassMemberOne = "yes";
                superSuperClassMemberTwo = "yes";
                superSuperClassMemberThree = "yes";
                superSuperClassMemberFour = "yes";
            }

            public void VerifyOne()
            {
                Assert.AreEqual("yes", superSuperClassMemberOne);
                Assert.AreEqual("yes", superSuperClassMemberTwo);
                Assert.AreEqual("yes", superSuperClassMemberThree);
                Assert.AreEqual("yes", superSuperClassMemberFour);
            }
        }

        public class SuperClassA : SuperSuperClassA
        {
            public string superClassMemberOne;
            private string superClassMemberTwo;
            internal string superClassMemberThree;
            protected string superClassMemberFour;

            public virtual void InitialseTwo()
            {
                superClassMemberOne = "yes";
                superClassMemberTwo = "yes";
                superClassMemberThree = "yes";
                superClassMemberFour = "yes";
            }

            public void VerifyTwo()
            {
                Assert.AreEqual("yes", superClassMemberOne);
                Assert.AreEqual("yes", superClassMemberTwo);
                Assert.AreEqual("yes", superClassMemberThree);
                Assert.AreEqual("yes", superClassMemberFour);
            }
        }

        [Liquifiable]
        public class SubClassA : SuperClassA
        {
            public string subClassMemberOne;
            private string subClassMemberTwo;
            internal string subClassMemberThree;
            protected string subClassMemberFour;

            public void InitialiseThree()
            {
                subClassMemberOne = "yes";
                subClassMemberTwo = "yes";
                subClassMemberThree = "yes";
                subClassMemberFour = "yes";
            }

            public void VerifyThree()
            {
                Assert.AreEqual("yes", subClassMemberOne);
                Assert.AreEqual("yes", subClassMemberTwo);
                Assert.AreEqual("yes", subClassMemberThree);
                Assert.AreEqual("yes", subClassMemberFour);
            }
        }

        [TestMethod()]
        public void Are_All_Members_In_Inheritence_Chain_Serialised_When_Serialising_Subclass()
        {
            SubClassA before = new SubClassA();

            before.InitialiseOne();
            before.InitialseTwo();
            before.InitialiseThree();

            SubClassA after = DoLiquifify(before);

            after.VerifyOne();
            after.VerifyTwo();
            after.VerifyThree();
        }

        #endregion

        #region Old Reference Integrity Test

        /* I can't remember everything this does and cannot work it out - it has been superceded by the nice integrity tests in LiquifierReferenceIntegrity.cs - but it is being
         * kept around because whatever it does it is hard and you can never have enough hard unit tests... */

        [Liquifiable]
        public class Class1
        {
            public Class1 newClass;
            public Class2 reference;

            public string id;

            public Class3 ac3;

            public Class1()
            {
                id = RandomString();
            }
        }
        [Liquifiable]
        public class Class2
        {
            public Class1 circularreference;
            public List<Class3> list = new List<Class3>();

            public List<Class1> listreference;

            public string id;

            public Class3 ac4;

            public Class2()
            {
                id = RandomString();
            }
        }
        [Liquifiable]
        public class Class3
        {
            public List<Class1> circularlist = new List<Class1>();
            public List<Class2> seperatelist = new List<Class2>();
            public List<Class2> referecnedlist = new List<Class2>();

            public List<Class2>[] listarray;

            public ObservableList<Class2> rlist = new ObservableList<Class2>();
            public ObservableList<Class2> rlist2 = new ObservableList<Class2>();

            public ObservableList<Class2>[] listarray2;

            public Class3 cls;

            public string id;

            public Class3()
            {
                id = RandomString();
            }
        }

        public Class1 CreateGraph()
        {
            Class1 c1 = new Class1();
            c1.newClass = new Class1();

            Class2 c2 = new Class2();
            c1.reference = c2;
            c2.circularreference = c1;

            List<Class1> lc1 = new List<Class1>();
            lc1.Add(c1);
            c2.listreference = lc1;

            Class3 c3 = new Class3();
            Class3 c4 = new Class3();

            c1.ac3 = c3;
            c2.ac4 = c4;

            c3.cls = c4;

            c3.circularlist = lc1;
            c4.circularlist = lc1;

            c3.listarray = new List<Class2>[] { c3.seperatelist, c4.seperatelist };
            c4.listarray = new List<Class2>[] { c3.referecnedlist, c4.referecnedlist };

            c3.listarray2 = new ObservableList<Class2>[] { c3.rlist, c4.rlist };
            c4.listarray2 = new ObservableList<Class2>[] { c3.rlist2, c4.rlist2 };

            c2.list.Add(c3);
            c2.list.Add(c4);

            c3.seperatelist.Add(new Class2());
            c3.referecnedlist.Add(c2);
            c4.seperatelist.Add(new Class2());
            c4.referecnedlist.Add(new Class2());

            c3.rlist2 = c4.rlist2;

            return c1;
        }

        public void VerifyGraph(Class1 c1)
        {
            Assert.AreNotSame(c1, c1.newClass);

            Class2 c2 = c1.reference;
            Assert.AreSame(c1, c2.circularreference);
            Class1 lc1 = c2.listreference[0];
            Assert.AreSame(c1, lc1);

            Class3 c3 = c1.reference.list[0];
            Class3 c4 = c1.reference.list[1];
            Assert.AreSame(c2.listreference, c3.circularlist);
            Assert.AreSame(c2.listreference, c4.circularlist);

            Assert.AreSame(c1.ac3, c3);
            Assert.AreSame(c2.ac4, c4);

            Assert.AreSame(c3.cls, c4);

            Assert.AreNotSame(c3.seperatelist, c4.seperatelist);

            Assert.AreSame(c3.referecnedlist[0], c2);
            Assert.AreNotSame(c4.referecnedlist[0], c2);

            //The contents of the array are not referenced because List<T> is not liquifiable and so will be formatted by binaryformatter.
            //This is because in ReadCollection (and write for that matter!) IsMasterCopy is FALSE because ReadObject is called by FastSerializer outside
            //the ReferenceObject object, as Array is not acctually Liquifiable, its just a special case in FastSerializer
            Assert.AreSame(c3.listarray[0], c3.seperatelist);
            Assert.AreSame(c3.listarray[1], c4.seperatelist);
            Assert.AreSame(c4.listarray[0], c3.referecnedlist);
            Assert.AreSame(c4.listarray[1], c4.referecnedlist);

            Assert.AreSame(c3.listarray[0][0], c3.seperatelist[0]);

            Assert.AreSame(c3.listarray2[0], c3.rlist);
            Assert.AreSame(c3.listarray2[1], c4.rlist);
            Assert.AreNotSame(c4.listarray2[0], c3.rlist2);
            Assert.AreSame(c4.listarray2[1], c4.rlist2);

            Assert.AreSame(c3.rlist2, c4.rlist2);

            Assert.AreNotSame(c3.listarray, c4.listarray);

        }


        [TestMethod()]
        public void Deprecated_But_Difficult_References_Test()
        {
            Class1 c1 = CreateGraph();

            MemoryTributary s = new MemoryTributary();
            Liquifer.Liquify(c1, s);
            s.Position = 0;
            Class1 cr = Liquifer.Solidify(s) as Class1;

            VerifyGraph(cr);
        }

        #endregion
    }
}
